#ifndef _COMPONENTS_H_
#define _COMPONENTS_H_

#include <GameObject/Component.h>
#include <GameObject/Object.h>
#include <Core/Uuid.h>
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <Renderer/Renderer.h>
#include <Core/Reflect.h>
#include <fstream>
#include <sstream>
#include <ScriptEngine/ScriptEngine.h>
#include <Log/Log.h>
#include <ScriptEngine/ScriptType.h>
#include <Renderer/Texture.h>

namespace Engine2D
{
    template<typename... Msg>
    struct ComponentWrapper : Component
    {
        ComponentWrapper() : m_msgs(std::make_tuple<Msg...>(Msg()...)) {}
        virtual ~ComponentWrapper() = default;
        void OnInit(IObject * obj) override
        {
            TupleForeach(m_msgs, [obj, this](auto elem) {
                obj->Subscribe<decltype(elem)>(this->shared_from_this());
            });
        }

        void OnMessage(Message* msg) override
        {
            TupleForeach(m_msgs, [msg](auto & elem) {
                if (dynamic_cast<std::decay_t<decltype(elem)>*>(msg)) {
                    elem = *dynamic_cast<std::decay_t<decltype(elem)>*>(msg);
                }
            });
        }

        template<typename T>
        const T & GetComponentMessage() { return std::get<T>(m_msgs); }

        std::tuple<Msg...> m_msgs;
    };

    struct TransformMessage : Message
    {
        glm::vec3 translate{0.0f, 0.0f, 0.0f};
        glm::vec3 scale{1.0f, 1.0f, 1.0f};
        float rotate = 0.0f;
        TransformMessage(glm::vec3 translate_ = glm::vec3(0.0f), glm::vec3 scale_ = glm::vec3(1.0f, 1.0f, 1.0f), float rotate_ = 0.0f) 
            : translate(translate_), scale(scale_), rotate(rotate_) {}
        glm::mat4 GetTransform() const
        {
            glm::mat4 trans(1.0f);
            trans = glm::translate(trans, translate);
            trans = glm::scale(trans, scale);
            trans = glm::rotate(trans, glm::radians(rotate), glm::vec3{0.0f, 0.0f, 1.0f});
            return trans;
        }
    };

    struct IDMessage : Message
    {
        Uuid uuid;
        IDMessage(Uuid id = Uuid()) : uuid(id) {}
        virtual ~IDMessage() = default;
    };

    struct NameComponent : Component
    {
        std::string name;
        NameComponent(const std::string & name_ = "defualt") : name(name_) { name.resize(256); };
        virtual ~NameComponent() = default;
    };
    REF_REGISTER_CLASS(NameComponent,
        REF_REGISTER_FIELD(NameComponent, name)
    )

    struct IDComponent : Component
    {
        Uuid uuid;
        IDComponent() = default;
        virtual ~IDComponent() = default;
        void OnTick(IObject * obj) override
        {
            obj->Send(IDMessage{uuid});
        }
    };
    REF_REGISTER_CLASS(IDComponent,
        REF_REGISTER_FIELD(IDComponent, uuid)
    )

    struct TagComponent : Component
    {
        std::string tag;
        TagComponent(const std::string & text = "") : tag(text) { tag.resize(1024); };
        virtual ~TagComponent() = default;
    };
    REF_REGISTER_CLASS(TagComponent,
        REF_REGISTER_FIELD(TagComponent, tag)
    )

    struct TransformComponent : Component
    {
        SLua_Vec3 translate{0.0f, 0.0f, 0.0f};
        SLua_Vec3 scale{1.0f, 1.0f, 1.0f};
        float rotate = 0.0f;
        TransformComponent() = default;
        virtual ~TransformComponent() = default;
        void OnTick(IObject * obj) override {
            obj->Send(TransformMessage{
                {translate.x, translate.y, translate.z},
                {scale.x, scale.y, scale.z},
                rotate
            });
        }
        glm::mat4 Transform() {
            return TransformMessage{
                {translate.x, translate.y, translate.z},
                {scale.x, scale.y, scale.z},
                rotate
            }.GetTransform();
        }
    };
    REF_REGISTER_CLASS(TransformComponent,
        REF_REGISTER_FIELD(TransformComponent, translate),
        REF_REGISTER_FIELD(TransformComponent, scale),
        REF_REGISTER_FIELD(TransformComponent, rotate)
    )

    struct RenderPolygonComponent : Component
    {
        std::vector<Vertex> vertices;
        RenderPolygonComponent(std::vector<Vertex> & points_ = std::vector<Vertex>()) : vertices(std::move(points_)) {}
        virtual ~RenderPolygonComponent() = default;
        void OnTick(IObject* obj) 
        {
            Renderer::Instance()->DrawPolygon(vertices);
        }
    };
    REF_REGISTER_CLASS(RenderPolygonComponent)

    struct RenderRectComponent : ComponentWrapper<TransformMessage>
    {
        int width, height;
        SLua_Vec4 color;
        RenderRectComponent(int width_ = 100, int height_ = 100, SLua_Vec4& color_ = SLua_Vec4{1.0f, 0.0f, 0.0f, 1.0f})
            : width(width_), height(height_), color(color_) {}
        virtual ~RenderRectComponent() = default;
        void OnTick(IObject* obj) 
        {
            std::vector<Vertex> vertices{
                Vertex{glm::vec4{-width / 2, -height / 2, 0, 1}, color.ToGlm() * 255.0f},
                Vertex{glm::vec4{width / 2, -height / 2, 0, 1}, color.ToGlm() * 255.0f},
                Vertex{glm::vec4{width / 2, height / 2, 0, 1}, color.ToGlm() * 255.0f},
                Vertex{glm::vec4{-width / 2, height / 2, 0, 1}, color.ToGlm() * 255.0f},
            };
            auto & trans = GetComponentMessage<TransformMessage>().GetTransform();
            for (auto && vertex : vertices) {
                vertex.pos = trans * vertex.pos;
            }
            std::vector<int> indices{0, 1, 2, 0, 2, 3};
            Renderer::Instance()->DrawPolygon(vertices, indices);
        }
    };
    REF_REGISTER_CLASS(RenderRectComponent, 
        REF_REGISTER_FIELD(RenderRectComponent, width),
        REF_REGISTER_FIELD(RenderRectComponent, height),
        REF_REGISTER_FIELD(RenderRectComponent, color)
    )

    struct ScriptComponent : Component
    {
        inline static bool s_doScript = false;
        std::string fileName;
        std::string script;
        ScriptComponent() { fileName.resize(256); }
        virtual ~ScriptComponent() = default;
        void Load() 
        {
            std::ifstream in(fileName);
            if (in.is_open()) {
                std::stringstream ss;
                ss << in.rdbuf();
                script = ss.str();
                in.close();
            }
        }
        void OnTick(IObject * obj) override {
            if (s_doScript) {
                // ScriptEngine::Instance().State()["Object"] = (size_t)obj; 
                ScriptEngine::Instance().Script(script);
            }
        }
    };
    REF_REGISTER_CLASS(ScriptComponent,
        REF_REGISTER_FIELD(ScriptComponent, fileName)
        // REF_REGISTER_FIELD(ScriptComponent, script)
    )

    struct CameraComponent : Component
    {
        bool isMainCamera = false;
        CameraComponent() = default;
        virtual ~CameraComponent() = default;
        void OnTick(IObject * obj) override {
            Renderer::Instance()->SetMainCamera(isMainCamera ? obj->shared_from_this() : nullptr);
        }
    };
    REF_REGISTER_CLASS(CameraComponent,
        REF_REGISTER_FIELD(CameraComponent, isMainCamera)
    )

    struct RenderTextureComponent : ComponentWrapper<TransformMessage>
    {
        std::shared_ptr<Texture> texture;
        SLua_Vec2 srcSize;
        SLua_Vec2 dstSize;
        // SLua_Rect srcRect;
        // SLua_Rect dstRect;
        std::string fileName;
        RenderTextureComponent();
        virtual ~RenderTextureComponent() = default;
        void Load();
        void OnTick(IObject * obj) override;
    };
    REF_REGISTER_CLASS(RenderTextureComponent,
        REF_REGISTER_FIELD(RenderTextureComponent, srcSize),
        REF_REGISTER_FIELD(RenderTextureComponent, dstSize),
        REF_REGISTER_FIELD(RenderTextureComponent, fileName)
    )

    using AllComponents = ComponentGroup<
        NameComponent, IDComponent, TagComponent, 
        TransformComponent, RenderPolygonComponent, RenderRectComponent,
        ScriptComponent, CameraComponent, RenderTextureComponent>;

    
} // namespace Engine2D


#endif